﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Collections.Concurrent;

namespace MinecraftProtocol.GameState
{
    public class GameState : TcpProxy.SessionState
    {
        const int MaxPos = 2147483647;

        public Entity Player { get; set; }
        public ConcurrentDictionary<int, Entity> Entities { get; set; }
        
        public Dictionary<Point3D, byte> Map { get; set; }

        //holds the number of times each block type is referenced
        public int[] BlockFrequency { get; set; }
        int HighestBlockCount { get; set; } //for normalisation

        public Point3D MapMin { get; set; }
        public Point3D MapMax { get; set; }

        public Point3D MapSize
        {
            get
            {
                return new Point3D(MapMax.X - MapMin.X + 1, MapMax.Y - MapMin.Y + 1, MapMax.Z - MapMin.Z + 1);
            }
        }

        public bool ServerDataBroken { get; set; }
        public bool ClientDataBroken { get; set; }

        /// <summary>
        /// Returns true if all required game data has been acquired
        /// </summary>
        public bool GameStateAcquired
        {
            get
            {
                return
                    (Map.Count() > 0) &&
                    (Player.Position.X != MaxPos);// &&
                //(PlayerView.Yaw != MaxPos);
            }
        }

        public GameState()
        {
            Reset();
        }

        public void Reset()
        {
            Map = new Dictionary<Point3D, byte>();

            Entities = new ConcurrentDictionary<int, Entity>();

            Player = new Entity();
            ServerDataBroken = false;
            ClientDataBroken = false;

            BlockFrequency = new int[256];
            HighestBlockCount = 0;

            Player.Position.X = MaxPos;
            Player.View.Yaw = MaxPos;
        }

        /// <summary>
        /// Update map extents and create block histogram (frequency)
        /// </summary>
        public void UpdateMapStats()
        {
            BlockFrequency = new int[256];
            HighestBlockCount = 0;

            Point3D min = new Point3D(MaxPos, MaxPos, MaxPos);
            Point3D max = new Point3D(-MaxPos, -MaxPos, -MaxPos);

            lock (Map)
            {
                foreach (KeyValuePair<Point3D, byte> kvp in Map)
                {
                    if (kvp.Key.X < min.X) min.X = kvp.Key.X;
                    if (kvp.Key.Y < min.Y) min.Y = kvp.Key.Y;
                    if (kvp.Key.Z < min.Z) min.Z = kvp.Key.Z;

                    if (kvp.Key.X > max.X) max.X = kvp.Key.X;
                    if (kvp.Key.Y > max.Y) max.Y = kvp.Key.Y;
                    if (kvp.Key.Z > max.Z) max.Z = kvp.Key.Z;

                    BlockFrequency[kvp.Value]++;

                    if (BlockFrequency[kvp.Value] > HighestBlockCount)
                    {
                        if ((kvp.Value != 0) && (kvp.Value != 1) && (kvp.Value != 3) && (kvp.Value != 9)) //stone, air, dirt and water are excluded 
                        {
                            HighestBlockCount = BlockFrequency[kvp.Value];
                        }
                    }
                }
            }

            MapMax = max;
            MapMin = min;
        }

        /// <summary>
        /// Returns KeyValuePairs of ([X,Z],[blocktype,Y])
        /// </summary>
        /// <param name="blockTypes"></param>
        /// <returns></returns>
        public Dictionary<Point, Point> GetTopmostBlocksByType(byte[] blockTypes)
        {

            Dictionary<Point, Point> ret = new Dictionary<Point, Point>();

            lock (Map)
            {
                foreach (KeyValuePair<Point3D, byte> kvp in Map)
                {
                    if (blockTypes.Contains(kvp.Value))
                    {
                        Point pt = new Point(kvp.Key.X, kvp.Key.Z);
                        if (ret.ContainsKey(pt))
                        {
                            //check whether this point has a higher Y (ie. closer to the top of the sky)
                            if (kvp.Key.Y > ret[pt].Y)
                                ret[pt] = new Point(kvp.Value, kvp.Key.Y);
                        }
                        else
                        {
                            ret.Add(pt, new Point(kvp.Value, kvp.Key.Y));
                        }
                    }
                }
            }

            return ret;
        }
    }
}
